// @Author EthanScriptOn
// @Desc
package tool

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_basic_components/util"
	"reflect"
	"strings"
)

// Builder Builder tools
type Builder struct {
	buildStruct     interface{}
	dispatcher      *MethodDispatcher
	errorCollection *ErrorCollection
}

// GetBuilder Get a builder based on type
func GetBuilder(buildTyp reflect.Type) *Builder {
	builder := new(Builder)
	builder.errorCollection = new(ErrorCollection)
	var buildValue interface{}
	if buildTyp.Kind() != reflect.Ptr {
		builder.errorCollection.WithErrorCollector(fmt.Errorf("%v must be a pointer type", buildTyp))
		return builder
	}
	buildValue = reflect.New(buildTyp.Elem()).Interface()
	builder.buildStruct = buildValue
	instance, err := GenerateMethodDispatcher(buildValue)
	if err != nil {
		builder.errorCollection.WithErrorCollector(err)
		return builder
	}
	builder.dispatcher = instance
	return builder
}
func GetInferenceBuilder() *Builder {
	return &Builder{
		errorCollection: new(ErrorCollection),
	}
}

// inferenceType Type derivation, which derives the builder instance based on the recipient of the method
func (c *Builder) inferenceType(methodVal interface{}) *Builder {
	if c.dispatcher != nil || c.errorCollection.GetError() != nil {
		return c
	}
	if reflect.TypeOf(methodVal).Kind() != reflect.Func {
		c.errorCollection.WithErrorCollector(fmt.Errorf("%v must be a function type", methodVal))
		return c
	}
	// Recipients may be included
	instanceNumIn := reflect.ValueOf(methodVal).Type().NumIn()
	// Exclude anonymous functions
	methodName := util.GetMethodNameWithMethod(methodVal)
	if instanceNumIn == 0 || strings.HasPrefix(methodName, "func") {
		/*
			Functions that don't have a referent
			suchAs: func NoReceiver(){}
			suchAs:
			type NoReceiverSt struct{}
			func (n NoReceiverSt)NoReceiverValue(){}
			func (n *NoReceiverSt)NoReceiverPointer(){}
			NoReceiverSt{}.NoReceiverValue
			new(NoReceiverSt).NoReceiverPointer
		*/
		c.errorCollection.WithErrorCollector(errors.New("please use methods with receiver type, such as (*Type).func"))
		return c
	}
	recipients := reflect.ValueOf(methodVal).Type().In(0)
	if recipients.Kind() != reflect.Ptr || recipients.Elem().Kind() != reflect.Struct {
		c.errorCollection.WithErrorCollector(errors.New("please use methods with pointer receiver type, such as (*Type).func"))
		return c
	}
	buildValue := reflect.New(recipients.Elem()).Interface()

	instance, err := GenerateMethodDispatcher(buildValue)
	if err != nil {
		c.errorCollection.WithErrorCollector(err)
		return c
	}

	if _, err = instance.GetStructFunctions().FoundMethod(methodName); err != nil {
		c.errorCollection.WithErrorCollector(fmt.Errorf("method dispatcher found method has error: [%v] please use methods with pointer receiver type, such as (*Type).func", err))
	}

	c.buildStruct = buildValue
	c.dispatcher = instance
	return c
}

// With Pass in the function of the object to be built to complete the build
func (c *Builder) With(methodInter interface{}, args ...interface{}) *Builder {
	if c.errorCollection.GetError() != nil {
		return c
	}
	// Type inference
	c.inferenceType(methodInter)
	if methodName := util.GetMethodNameWithMethod(methodInter); methodName == "" {
		c.errorCollection.WithErrorCollector(errors.New("method name is empty"))
		return c
	} else {
		return c.WithName(methodName, args...)
	}
}

// WithName Pass in the name of the function of the object being built to complete the build
func (c *Builder) WithName(methodName string, args ...interface{}) *Builder {
	if c.errorCollection.GetError() != nil {
		return c
	}
	if methodName == "" {
		c.errorCollection.WithErrorCollector(errors.New("method name is empty"))
	} else {
		if _, err := c.dispatcher.Call(methodName, args...); err != nil {
			c.errorCollection.WithErrorCollector(fmt.Errorf("method dispatcher call has error: [%v]", err))
		}
	}
	return c
}

// Build Returns the build result
func (c *Builder) Build() (instance interface{}, err error) {
	return c.buildStruct, c.errorCollection.GetError()
}
